<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Gif;

class GifDecoder
{
    private int $GIF_TransparentR           = -1;
    private int $GIF_TransparentG           = -1;
    private int $GIF_TransparentB           = -1;
    private int $GIF_TransparentI           =  0;
    private array $GIF_buffer               = [];
    private array $GIF_arrays               = [];
    private array $GIF_delays               = [];
    private array $GIF_dispos               = [];
    private string $GIF_stream              = '';
    private string $GIF_string              = '';
    private int $GIF_bfseek                 = 0;
    private int $GIF_anloop                 = 0;
    private array $GIF_screen               = [];
    private array $GIF_global               = [];
    private int $GIF_sorted                 = 0;
    private int $GIF_colorS                 = 0;
    private int $GIF_colorC                 = 0;
    private int $GIF_colorF                 = 0;

    /**
     * @param string $gifStream
     * __construct
     */
    public function __construct(string $gifStream)
    {
        $this->GIF_stream = $gifStream;
        $this->GIFGetByte(6);
        $this->GIFGetByte(7);
        $this->GIF_screen = $this->GIF_buffer;
        $this->GIF_colorF = $this->GIF_buffer[4] & 0x80 ? 1 : 0;
        $this->GIF_sorted = $this->GIF_buffer[4] & 0x08 ? 1 : 0;
        $this->GIF_colorC = $this->GIF_buffer[4] & 0x07;
        $this->GIF_colorS = 2 << $this->GIF_colorC;
        if($this->GIF_colorF == 1){
            $this->GIFGetByte(3 * $this->GIF_colorS);
            $this->GIF_global = $this->GIF_buffer;
        }
        for($cycle = 1; $cycle;){
            if($this->GIFGetByte(1)){
                switch($this->GIF_buffer[0]){
                    case 0x21:{
                        $this->GIFReadExtensions();
                        break;
                    }
                    case 0x2C:{
                        $this->GIFReadDescriptor();
                        break;
                    }
                    case 0x3B:{
                        $cycle = 0;
                        break;
                    }
                }
            }else {
                $cycle = 0;
            }
        }
    }

    /**
     * @return void
     * GIFReadExtensions
     */
    private function GIFReadExtensions()
    {
        $this->GIFGetByte(1);
        if($this->GIF_buffer[0] == 0xff){
            for(; ;){
                $this->GIFGetByte(1);
                if(( $u = $this->GIF_buffer[0]) == 0x00){
                    break;
                }
                $this->GIFGetByte($u);
                if($u == 0x03){
                    $this->GIF_anloop =($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8);
                }
            }
        }else{
            for(; ;){
                $this->GIFGetByte(1);
                if(( $u = $this->GIF_buffer[0]) == 0x00){
                    break;
                }
                $this->GIFGetByte($u);
                if($u == 0x04){
                    if(isset($this->GIF_buffer[4]) && $this->GIF_buffer[4] & 0x80){
                        $this->GIF_dispos[] =($this->GIF_buffer[0] >> 2) - 1;
                    }else{
                        $this->GIF_dispos[] =($this->GIF_buffer[0] >> 2);
                    }
                    $this->GIF_delays[] =($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8);
                    if($this->GIF_buffer[3]){
                        $this->GIF_TransparentI = $this->GIF_buffer[3];
                    }
                }
            }
        }
    }

    /**
     * @return void
     * GIFReadDescriptor
     */
    private function GIFReadDescriptor()
    {
        $this->GIFGetByte(9);
        $GIF_screen = $this->GIF_buffer;
        $GIF_colorF = $this->GIF_buffer[8] & 0x80 ? 1 : 0;
        if($GIF_colorF){
            $GIF_code = $this->GIF_buffer[8] & 0x07;
            $GIF_sort = $this->GIF_buffer[8] & 0x20 ? 1 : 0;
        }else {
            $GIF_code = $this->GIF_colorC;
            $GIF_sort = $this->GIF_sorted;
        }
        $GIF_size = 2 << $GIF_code;
        $this->GIF_screen[4] &= 0x70;
        $this->GIF_screen[4] |= 0x80;
        $this->GIF_screen[4] |= $GIF_code;
        if($GIF_sort){
            $this->GIF_screen[4] |= 0x08;
        }
        if($this->GIF_TransparentI){
            $this->GIF_string = 'GIF89a';
        }else {
            $this->GIF_string = 'GIF87a';
        }
        $this->GIFPutByte($this->GIF_screen);
        if($GIF_colorF == 1){
            $this->GIFGetByte(3 * $GIF_size);
            if($this->GIF_TransparentI){
                $this->GIF_TransparentR = $this->GIF_buffer[3 * $this->GIF_TransparentI];
                $this->GIF_TransparentG = $this->GIF_buffer[3 * $this->GIF_TransparentI + 1];
                $this->GIF_TransparentB = $this->GIF_buffer[3 * $this->GIF_TransparentI + 2];
            }
            $this->GIFPutByte($this->GIF_buffer);
        }else {
            if($this->GIF_TransparentI){
                $this->GIF_TransparentR = $this->GIF_global[3 * $this->GIF_TransparentI];
                $this->GIF_TransparentG = $this->GIF_global[3 * $this->GIF_TransparentI + 1];
                $this->GIF_TransparentB = $this->GIF_global[3 * $this->GIF_TransparentI + 2];
            }
            $this->GIFPutByte($this->GIF_global);
        }
        if($this->GIF_TransparentI){
            $this->GIF_string .= "!\xF9\x04\x1\x0\x0". chr($this->GIF_TransparentI) . "\x0";
        }
        $this->GIF_string .= chr(0x2C);
        $GIF_screen[8] &= 0x40;
        $this->GIFPutByte($GIF_screen);
        $this->GIFGetByte(1);
        $this->GIFPutByte($this->GIF_buffer);
        for(; ;){
            $this->GIFGetByte(1);
            $this->GIFPutByte($this->GIF_buffer);
            if(( $u = $this->GIF_buffer[0]) == 0x00){
                break;
            }
            $this->GIFGetByte($u);
            $this->GIFPutByte($this->GIF_buffer);
        }
        $this->GIF_string .= chr(0x3B);
        $this->GIF_arrays[] = $this->GIF_string;
    }

    /**
     * @param int $len
     * @return int
     * GIFGetByte
     */
    private function GIFGetByte(int $len):int
    {
        $this->GIF_buffer = [];
        for($i = 0; $i < $len; $i++){
            if($this->GIF_bfseek > strlen($this->GIF_stream)) return 0;
            $this->GIF_buffer[] = ord(substr($this->GIF_stream, $this->GIF_bfseek++, 1));
        }
        return 1;
    }

    /**
     * @param array $bytes
     * @return void
     * GIFPutByte
     */
    private function GIFPutByte(array $bytes)
    {
        if(count($bytes) > 0) foreach($bytes as $byte) $this->GIF_string .= chr($byte);
    }

    /**
     * @return array
     * getFrames
     */
    public function getFrames():array
    {
        return $this->GIF_arrays;
    }

    /**
     * @return array
     * getDelays
     */
    public function getDelays():array
    {
        return $this->GIF_delays;
    }

    /**
     * @return int
     * getLoop
     */
    public function getLoop():int
    {
        return $this->GIF_anloop;
    }

    /**
     * @return array
     * getDisposal
     */
    public function getDisposal():array
    {
        return $this->GIF_dispos;
    }

    /**
     * @return int
     * getTransparentR
     */
    public function getTransparentR():int
    {
        return $this->GIF_TransparentR;
    }

    /**
     * @return int
     * getTransparentG
     */
    public function getTransparentG():int
    {
        return $this->GIF_TransparentG;
    }

    /**
     * @return int
     * getTransparentB
     */
    public function getTransparentB():int
    {
        return $this->GIF_TransparentB;
    }
}